package com.jingge.autojob.util.http;

import com.google.common.util.concurrent.AtomicDouble;
import com.jingge.autojob.util.convert.StringUtils;
import com.jingge.autojob.util.id.SystemClock;
import com.jingge.autojob.util.progress.ProgressManager;
import com.jingge.autojob.util.thread.ScheduleTaskUtil;
import com.jingge.autojob.util.thread.SyncHelper;
import lombok.AccessLevel;
import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 文件下载辅助类，支持分段并发下载，支持断点下载
 *
 * @Author Huang Yongxiang
 * @Date 2022/05/27 15:56
 */
@Slf4j
public class FileDownloadHelper {
    /**
     * 文件元数据
     */
    private FileMetaData fileMetaData;
    /**
     * 请求方式
     */
    private String way;
    /**
     * 连接超时时长：ms
     */
    private int connectTimeout;
    /**
     * 读取数据超时时长：ms
     */
    private int readTimeout;
    /**
     * 分片数目
     */
    private int splitCount;
    /**
     * 完整数据的连接对象
     */
    private HttpURLConnection connection;

    private FileSplitFetchTask[] fetchTasks;

    private ProgressManager progressManager;
    /**
     * 下载速率
     */
    private double downloadRate;


    private FileDownloadHelper() {
    }

    public static Builder builder() {
        return new Builder();
    }

    private long getLength() {
        try {
            if (fileMetaData.length != -1) {
                return fileMetaData.length;
            }
            HttpURLConnection connection = getConnection();
            if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
                long length = connection.getContentLength();
                fileMetaData.length = length;
                String name = connection.getHeaderField("content-Disposition");
                if (!StringUtils.isEmpty(name)) {
                    fileMetaData.name = getFileName(URLDecoder.decode(name, "UTF-8"));
                }
                if (this.progressManager != null) {
                    this.progressManager.setTotalCount((int) length);
                }
                return length;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return -1;
    }

    private String getFileName(String contentDisposition) {
        int fileNamePos = contentDisposition
                .toLowerCase()
                .lastIndexOf("filename=");
        if (fileNamePos > 0) {
            return contentDisposition.substring(fileNamePos + "filename=".length());
        }
        return "emptyFileName";
    }

    private HttpURLConnection getConnection() {
        try {
            if (connection == null) {
                HttpURLConnection connection = (HttpURLConnection) new URL(fileMetaData.url).openConnection();
                connection.setReadTimeout(readTimeout);
                connection.setConnectTimeout(connectTimeout);
                connection.setRequestMethod(way);
                this.connection = connection;
                return connection;
            }
        } catch (Exception e) {
            log.error("获取连接时发生异常：{}", e.getMessage());
        }
        return connection;
    }

    private HttpURLConnection getSplitConnection(long startPos, long endPos) {
        HttpURLConnection connection = null;
        try {
            connection = (HttpURLConnection) new URL(fileMetaData.url).openConnection();
            connection.setReadTimeout(readTimeout);
            connection.setConnectTimeout(connectTimeout);
            connection.setRequestMethod(way);
            String prop = "bytes=" + startPos + "-" + endPos;
            //log.info("分片参数：{}", prop);
            connection.setRequestProperty("RANGE", prop);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return connection;
    }


    private InputStream getInputStreamFromConnection(HttpURLConnection connection) {
        try {
            return connection.getInputStream();
        } catch (IOException e) {
            log.error("获取输入流时发生异常：{}", e.getMessage());
        }
        return null;
    }

    public FileMetaData getFileMetaData() {
        return fileMetaData;
    }

    private FileSplitFetchTask[] createFileSplitFetchTask() {
        FileSplitFetchTask[] fetchTasks = new FileSplitFetchTask[splitCount];
        long length = getLength();
        if (splitCount == 1 || length == -1) {
            return new FileSplitFetchTask[]{new FileSplitFetchTask(0, length, 0)};
        }
        if (length == -2) {
            log.error("文件：{}不存在", fileMetaData.name);
            return fetchTasks;
        }
        int lastEndPos = 0;
        int startPos = 0;
        int endPos = 0;
        double averageLength = (length * 1.0) / splitCount;
        for (int i = 0; i < splitCount; i++) {
            if (lastEndPos != 0) {
                startPos = lastEndPos + 1;
            }
            endPos = startPos + (int) Math.ceil(averageLength);
            fetchTasks[i] = new FileSplitFetchTask(startPos, endPos, i);
            lastEndPos = endPos;
        }
        return fetchTasks;
    }


    public InputStream getInputStream() {
        return fileMetaData.getInputStream();
    }

    public double getDownloadProgress() {
        if (progressManager != null) {
            return progressManager.getFinishedPercent();
        }
        return 0;
    }

    public double getDownloadRate() {
        return this.downloadRate;
    }


    public boolean write() {
        try {
            if (fileMetaData.content != null && fileMetaData.content.size() > 0 && !StringUtils.isEmpty(fileMetaData.path)) {
                File file = new File(fileMetaData.path + File.separator + fileMetaData.name);
                FileOutputStream outputStream = new FileOutputStream(file);
                outputStream.write(fileMetaData.getContentAsByteArray());
                outputStream.flush();
                outputStream.close();
                return true;
            } else {
                log.error("没有指定写入路径，写入失败");
                return false;
            }
        } catch (Exception e) {
            log.error("写入时发生异常：{}", e.getMessage());
            e.printStackTrace();
        }
        return false;
    }

    public void stopDownload() {
        if (this.fetchTasks != null) {
            for (FileSplitFetchTask task : fetchTasks) {
                task.stop();
            }
        }
    }

    public void continueDownload() {
        if (this.fetchTasks != null) {
            for (FileSplitFetchTask task : fetchTasks) {
                task.goOn();
            }
        }
    }

    public void download() {
        int totalCount = 0;
        try {
            //构建分片任务对象
            FileSplitFetchTask[] fetchTasks = createFileSplitFetchTask();
            this.fetchTasks = fetchTasks;
            List<FutureTask<Integer>> futureTasks = new ArrayList<>();
            for (int i = 0; i < fetchTasks.length; i++) {
                FutureTask<Integer> futureTask = new FutureTask<>(fetchTasks[i]);
                futureTasks.add(futureTask);
                Thread thread = new Thread(futureTask);
                thread.setName(String.valueOf(i));
                //启动下载
                thread.start();
            }

            //阻塞等待所有线程下载完
            if (progressManager != null) {
                startProgressVisitor();
            }
            for (FutureTask<Integer> future : futureTasks) {
                totalCount += future.get();
            }
            //拼接内容
            for (FileSplitFetchTask task : fetchTasks) {
                byte[] cache = task.getContent();
                for (byte b : cache) {
                    fileMetaData.content.add(b);
                }
            }
            //写出
            if (!StringUtils.isEmpty(fileMetaData.path) && write()) {
                log.info("写出成功，路径：{}", fileMetaData.path + File.separator + fileMetaData.name);
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("下载过程发生异常：{}", e.getMessage());
            return;
        }
        if (totalCount != fileMetaData.length && fileMetaData.length > 0) {
            log.warn("下载字节数：{}与实际字节数：{}不匹配", totalCount, fileMetaData.length);
        } else {
            //log.info("下载成功");
        }

    }

    public void clear() {
        this.fileMetaData = null;
        connection.disconnect();
        connection = null;
        //System.gc();
    }

    private static String urlEncodeChinese(String url) {
        try {
            Matcher matcher = Pattern
                    .compile("[\\u4e00-\\u9fa5]")
                    .matcher(url);
            String tmp = "";
            while (matcher.find()) {
                tmp = matcher.group();
                url = url.replaceAll(tmp, URLEncoder.encode(tmp, "UTF-8"));
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return url.replace(" ", "%20");
    }

    /**
     * 下载文件的元数据
     */
    @Setter(AccessLevel.PRIVATE)
    private static class FileMetaData {
        /**
         * 地址
         */
        private String url;
        /**
         * 长度
         */
        private long length = -1;
        /**
         * 写入路径
         */
        private String path;
        /**
         * 文件名，包含后缀
         */
        private String name;
        /**
         * 后缀，不包含.
         */
        private String suffix;
        /**
         * 内容，二进制字列表
         */
        private List<Byte> content;

        private byte[] arrayContent;
        /**
         * 文件的输入流
         */
        private InputStream inputStream;

        public byte[] getContentAsByteArray() {
            if (arrayContent != null) {
                return arrayContent;
            }
            if (content != null) {
                int i = 0;
                byte[] holder = new byte[content.size()];
                for (Byte bt : content) {
                    holder[i++] = bt;
                }
                arrayContent = holder;
                return holder;

            }
            return new byte[]{};
        }

        public InputStream getInputStream() {
            if (inputStream == null) {
                inputStream = new ByteArrayInputStream(getContentAsByteArray());
            }
            return inputStream;
        }
    }

    private void startProgressVisitor() {
        AtomicDouble lastProgress = new AtomicDouble(0);
        AtomicInteger lastCount = new AtomicInteger(0);
        AtomicLong lastTime = new AtomicLong(System.currentTimeMillis());
        AtomicDouble rate = new AtomicDouble(0);
        Runnable runnable = () -> {
            if (progressManager.isAchieveThreshold(lastProgress.get())) {
                long interval = System.currentTimeMillis() - lastTime.get();
                if (interval > 1000) {
                    rate.set(((progressManager.getFinishedCount() - lastCount.get()) / (1024.0)) / (interval / 1000.0));
                } else {
                    rate.set(((progressManager.getFinishedCount() - lastCount.get()) / (1024.0)) / (1000.0 / interval));
                }
                lastTime.set(System.currentTimeMillis());
                lastProgress.set(progressManager.getNowGetProgress());
                lastCount.set(progressManager.getFinishedCount());
                downloadRate = rate.get();
                log.info(String.format("当前总进度：%.2f%%，最近下载速率：%.4fKB/s", progressManager.getNowGetProgress(), rate.get()));
            }
        };
        ScheduleTaskUtil
                .build(true, "downloadProgressVisitor")
                .EFixedRateTask(runnable, 1, 1, TimeUnit.MILLISECONDS);
    }

    @Setter
    @Accessors(chain = true)
    public static class Builder {
        /**
         * 地址
         */
        private String url;
        /**
         * 写入路径
         */
        private String path;
        /**
         * 文件名，包含后缀
         */
        private String name;
        /**
         * 请求方式
         */
        private String way = "get";
        /**
         * 连接超时时长：ms
         */
        private int connectTimeout = 5000;
        /**
         * 读取数据超时时长：ms
         */
        private int readTimeout = 5000;
        /**
         * 是否允许分片下载
         */
        private boolean allowSplitDownload = true;
        /**
         * 分片数目
         */
        private int splitCount = 3;
        /**
         * 是否记录进度
         */
        private boolean allowRecordProgress = false;


        public Builder setConnectTimeout(int connectTimeout, TimeUnit unit) {
            this.connectTimeout = (int) unit.toMillis(connectTimeout);
            return this;
        }

        public Builder setReadTimeout(int readTimeout, TimeUnit unit) {
            this.readTimeout = (int) unit.toMillis(readTimeout);
            return this;
        }

        public FileDownloadHelper build() {
            if (!check()) {
                throw new IllegalArgumentException("错误参数，请检查");
            }
            FileDownloadHelper fileDownloadHelper = new FileDownloadHelper();
            FileMetaData fileMetaData = new FileMetaData();
            if (!StringUtils.isEmpty(name)) {
                fileMetaData.setName(name);
                int pos = fileMetaData.name.lastIndexOf(".");
                if (pos != -1) {
                    fileMetaData.setSuffix(fileMetaData.name.substring(pos));
                }
            } else if (url.lastIndexOf("?") == -1) {
                int namePos = url
                        .trim()
                        .lastIndexOf("/");
                fileMetaData.name = url.substring(namePos + 1);
            }
            fileMetaData.setPath(path);
            fileMetaData.setUrl(url.trim());
            fileMetaData.content = new ArrayList<>();
            if (way
                    .trim()
                    .equalsIgnoreCase("get") || way
                    .trim()
                    .equalsIgnoreCase("post")) {
                fileDownloadHelper.way = way
                        .trim()
                        .toUpperCase();
            }
            if (allowRecordProgress && allowSplitDownload && splitCount > 1) {
                fileDownloadHelper.progressManager = new ProgressManager();
                fileDownloadHelper.progressManager.setChangeThresholdPercent(1.0, "PERCENT");
            }
            fileDownloadHelper.connectTimeout = connectTimeout;
            fileDownloadHelper.readTimeout = readTimeout;
            fileDownloadHelper.fileMetaData = fileMetaData;
            fileDownloadHelper.splitCount = allowSplitDownload ? splitCount : 1;
            return fileDownloadHelper;
        }

        private boolean check() {
            //boolean flag = url.lastIndexOf("/") != -1 || url.lastIndexOf(File.separator) != -1;
            return !StringUtils.isEmpty(url) && splitCount > 0;
        }

        public Builder setUrl(String url) {
            this.url = urlEncodeChinese(url);
            return this;
        }
    }

    private class FileSplitFetchTask implements Callable<Integer> {
        /**
         * 开始索引
         */
        private final long startPos;
        /**
         * 终止索引
         */
        private final long endPos;
        /**
         * 开始标志
         */
        private boolean isStart = false;
        /**
         * 结束标志
         */
        private boolean isOver = false;
        /**
         * 暂停标志
         */
        private AtomicBoolean isStop = new AtomicBoolean(false);
        /**
         * 线程号
         */
        private final int threadId;
        /**
         * 内容
         */
        private byte[] content;
        private List<Byte> contentList;
        /**
         * 分片请求
         */
        private HttpURLConnection splitConnection;

        public FileSplitFetchTask(long startPos, long endPos, int threadId) {
            //if (endPos < startPos) {
            //    throw new IllegalArgumentException("终止索引不得小于起始索引");
            //}
            this.startPos = startPos;
            this.endPos = endPos;
            this.threadId = threadId;
            if (endPos >= 0) {
                this.content = new byte[(int) (endPos - startPos + 1)];
                this.splitConnection = getSplitConnection(startPos, endPos);
            } else {
                this.contentList = new ArrayList<>();
            }

        }


        public byte[] getContent() {
            if (isOver) {
                if (content != null) {
                    return content;
                } else {
                    byte[] con = new byte[contentList.size()];
                    for (int i = 0; i < contentList.size(); i++) {
                        con[i] = contentList.get(i);
                    }
                    return con;
                }
            } else {
                log.error("线程：{}正在拉取，无法获取", threadId);
                return null;
            }
        }

        public void stop() {
            log.info("线程：{}已暂停下载", threadId);
            this.isStop.set(true);
        }

        public void goOn() {
            log.info("线程：{}已继续下载", threadId);
            this.isStop.set(false);
        }

        public boolean isStart() {
            return isStart;
        }

        public boolean isOver() {
            return isOver;
        }

        public boolean isStop() {
            return isStop.get();
        }

        @Override
        public Integer call() throws Exception {
            long start = System.currentTimeMillis();
            InputStream inputStream = getInputStreamFromConnection(splitConnection == null ? getConnection() : splitConnection);
            if (inputStream == null) {
                throw new NullPointerException("线程：" + threadId + "下载失败，输入流为空");
            }
            int cache;
            try {
                isStart = true;
                int pos = 0;
                while (true) {
                    SyncHelper.aWaitQuietly(() -> !isStop.get());
                    cache = inputStream.read();
                    if (cache != -1) {
                        if (progressManager != null) {
                            progressManager.increment();
                        }
                        if (content != null) {
                            content[pos++] = (byte) cache;
                        } else {
                            contentList.add((byte) cache);
                        }
                    } else {
                        break;
                    }
                }
                //log.info("线程：{}已下载完，共计：{}字节，共计用时：{}ms", threadId, pos, System.currentTimeMillis() - start);
                inputStream.close();
                return pos;
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                isStop.set(true);
                isOver = true;
            }
            return 0;
        }
    }

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        AtomicBoolean over = new AtomicBoolean(false);
        String url = null;
        url = "http://subsys.sctower.cn/msapi/attach/download?name=511524500000000088-分摊确认单.jpg&basepath=V3VZZU1hbmFnZS9VcGxvYWRGaWxlLzIwMjIxMQ==";

        FileDownloadHelper downloadHelper = FileDownloadHelper
                .builder()
                .setUrl(url)
                .setWay("get")
                .setAllowSplitDownload(true)
                .setSplitCount(3)
                .setConnectTimeout(60, TimeUnit.SECONDS)
                .setReadTimeout(60, TimeUnit.SECONDS)
                .setAllowRecordProgress(false)
                .setPath("C:\\Users\\鲸哥\\Desktop")
                .build();
        //System.out.println(urlEncodeChinese("http://你好吗.jpg"));
        downloadHelper.download();
        //SyncHelper.sleepQuietly(3, TimeUnit.SECONDS);
        //downloadHelper.stopDownload();
        //SyncHelper.sleepQuietly(3, TimeUnit.SECONDS);
        //downloadHelper.continueDownload();


        //for (String url : urls) {
        //    Runnable runnable = () -> {
        //        FileDownloadHelper downloadHelper = FileDownloadHelper.builder().setUrl(url).setWay("get").setAllowSplitDownload(true).setSplitCount(4).build();
        //        downloadHelper.download();
        //        over.set(true);
        //    };
        //    Thread thread = new Thread(runnable);
        //    thread.start();
        //}
        //do {
        //    try {
        //        Thread.sleep(1);
        //    } catch (InterruptedException e) {
        //        e.printStackTrace();
        //    }
        //} while (!over.get());
        System.out.println("下载完成，总计用时：" + (System.currentTimeMillis() - start) + "ms");
    }

}
